Skip to content

http: resume kept-alive when no body allowed - #52329

Open
mweberxyz wants to merge 2 commits into
nodejs:mainfrom
mweberxyz:http-keep-alive-auto-resume
Open

http: resume kept-alive when no body allowed#52329
mweberxyz wants to merge 2 commits into
nodejs:mainfrom
mweberxyz:http-keep-alive-auto-resume

Conversation

@mweberxyz

@mweberxyz mweberxyz commented Apr 2, 2024

Copy link
Copy Markdown

In accordance with https://www.rfc-editor.org/rfc/rfc9112#name-message-body-length: HEAD, 1xx, 204, and 304 responses cannot contain a message body.

If a connection will be kept-alive, resume the socket during parsing so that it may be returned to the free pool, even if the caller has not consumed the known-expected-empty response.

Fixes: #47228

Note: The original source of the issue reporter's issue was due to failure to consume the response body of a 3xx request, which this change will not resolve. As an option, 3xx with a Content-Length of 0 could receive the same auto-resume treatment. I am hesitant to propose that change, as some HTTP servers may return a message body (ie <a href=here>Moved Here</a>), which would then make the caller's responsibility to consume or resume dependent on the particular remote server's behavior.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/http
  • @nodejs/net

@nodejs-github-bot nodejs-github-bot added http Issues or PRs related to the http subsystem. needs-ci PRs that need a full CI run. labels Apr 2, 2024

@ShogunPanda ShogunPanda left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit. Rest LGTM.

Comment thread lib/_http_client.js Outdated
if (!shouldKeepAlive) {
req.shouldKeepAlive = false;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this space.

@mweberxyz
mweberxyz marked this pull request as draft April 5, 2024 15:53
@mweberxyz

Copy link
Copy Markdown
Author

Setting to draft, went to fix review nit and add tests and I am not convinced issue is fully resolved.

@mweberxyz
mweberxyz force-pushed the http-keep-alive-auto-resume branch from 2de97af to be6b085 Compare April 5, 2024 17:52
@mweberxyz
mweberxyz marked this pull request as ready for review April 5, 2024 17:52
@mweberxyz

Copy link
Copy Markdown
Author

@ShogunPanda I added tests and discovered that my approach putting the logic in in an else if following the !shouldKeepAlive condition will work for 204 and 304s, but not for HEAD, because at this point shouldKeepAlive is set to false for HEAD requests -- this is coming from llhttp.

@mweberxyz
mweberxyz requested a review from ShogunPanda April 5, 2024 17:55
@mweberxyz
mweberxyz force-pushed the http-keep-alive-auto-resume branch from be6b085 to 2dee82a Compare April 5, 2024 19:06

@mcollina mcollina left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@mcollina mcollina added the request-ci Add this label to start a Jenkins CI on a PR. label Apr 9, 2024
@ronag

ronag commented Apr 9, 2024

Copy link
Copy Markdown
Member

This is potentially unsafe. Some servers don't properly end HEAD requests and there is no way to detect it. Such a case would potentially corrupt following responses from the same connection. Which is why in undici we always reset the connection after a HEAD request, unless explicitly told otherwise (reset: false option).

@mweberxyz

Copy link
Copy Markdown
Author

@ronag re-using a socket following a HEAD request is the existing logic.

I checked with the following replication:

const http = require("http");

const noKeepalive = process.argv.at(-1) === "no-keepalive";

const opts = {};

if (noKeepalive) {
  opts.agent = new http.Agent({ keepAlive: false });
}

console.log({
  version: process.version,
  keepAlive: !noKeepalive,
});

http.get("http://www.example.com", { method: "HEAD", ...opts }, (res) => {
  res.resume();
  const headSocket = res.socket;
  setTimeout(() => {
    http.get("http://www.example.com", { method: "GET", ...opts }, (res2) => {
      console.log({ sameSocket: headSocket === res2.socket });
    });
  }, 500);
});

gives:

% node index.js keepalive
{ version: 'v20.12.1', keepAlive: true }
{ sameSocket: true }

and

% node index.js no-keepalive
{ version: 'v20.12.1', keepAlive: false }
{ sameSocket: false }

Should I take a look at that first before returning to this PR?

@ronag

ronag commented Apr 9, 2024

Copy link
Copy Markdown
Member

What does this PR do then?

@ronag

ronag commented Apr 9, 2024

Copy link
Copy Markdown
Member

I think the problem might apply to all expected empty responses. I need to check that for undici as well.

@mweberxyz

Copy link
Copy Markdown
Author

What does this PR do then?

Makes it so the res.resume() following a HEAD request, a request that returns a 204, or a request that returns a 304 is implicit.

@ronag

ronag commented Apr 9, 2024

Copy link
Copy Markdown
Member

Makes it so the res.resume() following a HEAD request, a request that returns a 204, or a request that returns a 304 is implicit.

Sorry, I don't understand.

@ronag ronag left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I get it. LGTM.

@mweberxyz

mweberxyz commented Apr 9, 2024

Copy link
Copy Markdown
Author

FWIW what lead me here is a package that was using node:https and not consuming the response on a request that returned a 302, which prevented node from exiting until the keepAlive timed out. I can't fix that because a 302 could return a response, so it should be up to the caller to avoid keeping node from exiting, but figured in these three cases where an http request could not possibly return a response, per spec, we can safely do this automatically.

Will push changes for lint errors shortly.

@mweberxyz
mweberxyz force-pushed the http-keep-alive-auto-resume branch from 2dee82a to 8c4db34 Compare April 9, 2024 16:41
@mweberxyz

Copy link
Copy Markdown
Author

In a twist of irony, my test appears to have left a socket open. Will take a look and push fix.

@mweberxyz
mweberxyz force-pushed the http-keep-alive-auto-resume branch from 8c4db34 to cf6677a Compare April 9, 2024 20:03
@mweberxyz

Copy link
Copy Markdown
Author

Ready for ci: the server in the test needed to be unref'd.

@ShogunPanda ShogunPanda left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@mweberxyz

Copy link
Copy Markdown
Author

I can't replicate that final test-asan failure locally - let me know if there's anything else I need to do to get this ready to land 👍

@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Apr 10, 2024
@nodejs-github-bot

This comment was marked as outdated.

@nodejs-github-bot

This comment was marked as outdated.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@aduh95 aduh95 added the commit-queue Add this label to land a pull request using GitHub Actions. label Jul 28, 2025
@nodejs-github-bot nodejs-github-bot added commit-queue-failed An error occurred while landing this pull request using GitHub Actions. and removed commit-queue Add this label to land a pull request using GitHub Actions. labels Jul 28, 2025
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator
Commit Queue failed
- Loading data for nodejs/node/pull/52329
✔  Done loading data for nodejs/node/pull/52329
----------------------------------- PR info ------------------------------------
Title      http: resume kept-alive when no body allowed (#52329)
Author     Matt Weber <hello@mweber.xyz> (@mweberxyz, first-time contributor)
Branch     mweberxyz:http-keep-alive-auto-resume -> nodejs:main
Labels     http, semver-minor, author ready, needs-ci
Commits    1
 - http: resume kept-alive when no body allowed
Committers 1
 - Antoine du Hamel <duhamelantoine1995@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/52329
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Minwoo Jung <nodecorelab@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
------------------------------ Generated metadata ------------------------------
PR-URL: https://github.com/nodejs/node/pull/52329
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Minwoo Jung <nodecorelab@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
--------------------------------------------------------------------------------
   ℹ  This PR was created on Tue, 02 Apr 2024 17:41:06 GMT
   ✔  Approvals: 5
   ✔  - Paolo Insogna (@ShogunPanda) (TSC): https://github.com/nodejs/node/pull/52329#pullrequestreview-1991621837
   ✔  - Matteo Collina (@mcollina) (TSC): https://github.com/nodejs/node/pull/52329#pullrequestreview-2722000587
   ✔  - Robert Nagy (@ronag) (TSC): https://github.com/nodejs/node/pull/52329#pullrequestreview-1989549614
   ✔  - Minwoo Jung (@JungMinu): https://github.com/nodejs/node/pull/52329#pullrequestreview-2228985328
   ✔  - James M Snell (@jasnell) (TSC): https://github.com/nodejs/node/pull/52329#pullrequestreview-2744925503
   ✔  Last GitHub CI successful
   ℹ  Last Full PR CI on 2025-07-11T07:24:00Z: https://ci.nodejs.org/job/node-test-pull-request/67928/
- Querying data for job/node-test-pull-request/67928/
   ✔  Last Jenkins CI successful
   ⚠  PR author is a new contributor: @mweberxyz(hello@mweber.xyz)
   ⚠  - commit a38c7aeac7f6 is authored by 1062734+mweberxyz@users.noreply.github.com
--------------------------------------------------------------------------------
   ✔  Aborted `git node land` session in /home/runner/work/node/node/.ncu
https://github.com/nodejs/node/actions/runs/16581371844

@mcollina mcollina left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@mcollina mcollina added request-ci Add this label to start a Jenkins CI on a PR. and removed commit-queue-failed An error occurred while landing this pull request using GitHub Actions. labels Jul 29, 2025
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Jul 29, 2025
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@aduh95 aduh95 added the request-ci Add this label to start a Jenkins CI on a PR. label Aug 27, 2025
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Aug 27, 2025
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@addaleax addaleax added the request-ci Add this label to start a Jenkins CI on a PR. label Sep 24, 2025
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Sep 24, 2025
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@mcollina mcollina added the request-ci Add this label to start a Jenkins CI on a PR. label Feb 1, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Feb 1, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@Ethan-Arrowood

Copy link
Copy Markdown
Contributor

Sorry to see this stall out. Can you rebase and see what might be failing in CI? Would love to help you land this!

According to RFC9112 section 6.3.1: HEAD requests, and responses
with status 204 and 304 cannot contain a message body,

If a socket will be kept-alive, resume the socket during parsing
so that it may be returned to the free pool.

Fixes nodejs#47228
@aduh95
aduh95 force-pushed the http-keep-alive-auto-resume branch from a38c7ae to b5c8c84 Compare August 5, 2026 15:43
Comment thread test/parallel/test-http-agent-exit.js Outdated
Comment thread test/parallel/test-http-agent-exit.js Outdated
Co-authored-by: Antoine du Hamel <duhamelantoine1995@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

author ready PRs that have at least one approval, no pending requests for changes, and a CI started. http Issues or PRs related to the http subsystem. needs-ci PRs that need a full CI run. semver-minor PRs that contain new features and should be released in the next minor version.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Process does not exit after https request with keepAlive enabled (now default in Node 19)